/*
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;

using Gtk;
using Glade;
using Mono.Unix;

using DCSharp.Backend.Connections;
using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;
using DCSharp.Xml;

namespace DCSharp.Gui
{
	public class MainWindow : GladeWindow
	{
		public event EventHandler<PageEventArgs> PageAdded;
		public event EventHandler<PageEventArgs> PageRemoved;
		public event EventHandler<PageEventArgs> CurrentPageChanged;

		private IFavoriteHubManager favoriteHubManager;

		private Box pageManagerBox;
		private PageManager pageManager;

		private Page currentPage;
		private ActionGroup pageActions;
		private uint mergeId;
		private Dictionary<MenuItem, Action> itemToAction;

		private UIManager uim;
		private ActionGroup actionGroup;
		private Action closeAction;
		private ToggleAction toolbarVisibleAction;
		private RadioAction layoutTabsAction;

		private Toolbar toolbar;
		private MenuToolButton hubToolButton;

		private bool isWorking;
		private uint timeout;

		private static Gdk.Cursor normalCursor = null;
		private static Gdk.Cursor workingCursor = new Gdk.Cursor(Gdk.CursorType.Watch);

		[Widget]
		private VBox mainBox;

		[Widget]
		private Box statusBox;

		[Widget]
		private Statusbar statusbar;

		[Widget]
		private ProgressBar progressBar;

		#region Constructors

		public MainWindow(ConnectionManager connectionManager,
			IFavoriteHubManager favoriteHubManager) : base("MainWindow.glade")
		{
			if (favoriteHubManager == null)
			{
				throw new ArgumentNullException("favoriteHubManager");
			}
			this.favoriteHubManager = favoriteHubManager;

			itemToAction = new Dictionary<MenuItem, Action>();

			Window.Title = Util.AppName;

			ActionEntry[] entries = new ActionEntry[] {
				new ActionEntry("FileMenu", null, Catalog.GetString("_File"), null, null, null),
				new ActionEntry("Hubs", Stock.Network, Catalog.GetString("_Hubs"), "<control>H",
					Catalog.GetString("Edit hubs"),
					OnHubs),
				new ActionEntry("Search", Stock.Find, Catalog.GetString("_Search..."), "<control>F",
					Catalog.GetString("Search for files"),
					OnSearch),
				new ActionEntry("Preferences", Stock.Preferences, Catalog.GetString("_Preferences"), null,
					Catalog.GetString("Edit the preferences"),
					OnPreferences),
				new ActionEntry("Close", Stock.Close, Catalog.GetString("_Close"), "<control>W",
					Catalog.GetString("Close the current page"),
					OnClose),
				new ActionEntry("Quit", Stock.Quit, Catalog.GetString("_Quit"), "<control>Q",
					Catalog.GetString("Quit the program"),
					OnQuit),

				new ActionEntry("ViewMenu", null, Catalog.GetString("_View"), null, null, null),
				new ActionEntry("Downloads", Stock.GoDown, Catalog.GetString("_Downloads"), "<control>D",
					Catalog.GetString("Active downloads"),
					OnDownloads),
				new ActionEntry("Uploads", Stock.GoUp, Catalog.GetString("_Uploads"), "<control>U",
					Catalog.GetString("Active uploads"),
					OnUploads),
				new ActionEntry("VisibleColumns", null, Catalog.GetString("_Visible Columns..."), null,
					Catalog.GetString("Edit visible columns"),
					null),

				new ActionEntry("ActionMenu", null, Catalog.GetString("_Actions"), null, null, null),
				new ActionEntry("Start", Stock.MediaPlay, Catalog.GetString("_Start"), null, null, null),
				new ActionEntry("Stop", Stock.MediaStop, Catalog.GetString("S_top"), null, null, null),
				new ActionEntry("Remove", Stock.Remove, null, null, null, null),
				new ActionEntry("Clear", Stock.Clear, Catalog.GetString("_Clean Up"), null, null, null),

				new ActionEntry("HelpMenu", null, Catalog.GetString("_Help"), null, null, null),
				new ActionEntry("About", Stock.About, Catalog.GetString("_About"), null,
					Catalog.GetString("About this program"),
					OnAbout)
			};

			ToggleActionEntry[] toggleEntries = new ToggleActionEntry[] {
				new ToggleActionEntry("ToggleToolbar", null, Catalog.GetString("_Toolbar"), null,
					Catalog.GetString("Show or hide the toolbar"),
					OnToggleToolbar, true)
			};

			RadioActionEntry[] layoutRadioEntries = new RadioActionEntry[] {
				new RadioActionEntry("LayoutTabs", null, Catalog.GetString("T_abs"), null,
					Catalog.GetString("View as tabs"), (int)PageStyle.Tabs),
				new RadioActionEntry("LayoutTree", null, Catalog.GetString("_List"), null,
					Catalog.GetString("View as list"), (int)PageStyle.List)
			};

			actionGroup = new ActionGroup("MainWindowActions");
			actionGroup.Add(entries);
			actionGroup.Add(toggleEntries);
			actionGroup.Add(layoutRadioEntries, 0, OnLayoutChanged);

			uim = new UIManager();
			uim.AddWidget += OnWidgetAdd;
			uim.ConnectProxy += OnConnectProxy;
			uim.DisconnectProxy += OnDisconnectProxy;

			uim.InsertActionGroup(actionGroup, 0); 
			uim.AddUiFromResource(Util.GetResource("MainWindow.ui"));

			Window.AddAccelGroup(uim.AccelGroup);

			closeAction = actionGroup["Close"];
			toolbarVisibleAction = (ToggleAction)actionGroup["ToggleToolbar"];
			layoutTabsAction = (RadioAction)actionGroup["LayoutTabs"];

			closeAction.Sensitive = false;
			actionGroup["Search"].IsImportant = true;
			actionGroup["Start"].Sensitive = false;
			actionGroup["Stop"].Sensitive = false;
			actionGroup["Remove"].Sensitive = false;
			actionGroup["Clear"].Sensitive = false;
			actionGroup["VisibleColumns"].Sensitive = false;

			// Hub Toolbutton
			hubToolButton = new MenuToolButton(Stock.Ok);
			actionGroup["Hubs"].ConnectProxy(hubToolButton);

			hubToolButton.Menu = favoriteHubManager.HubCount != 0 ? new Menu() : null;
			hubToolButton.ShowMenu += delegate
			{
				UpdateFavoriteHubMenu(hubToolButton);
			};

			toolbar = (Toolbar)uim.GetWidget("/ToolBar");
			toolbar.Insert(hubToolButton, 0);

			// Page Manager
			pageManagerBox = new HBox();
			pageManagerBox.Show();
			mainBox.PackEnd(pageManagerBox, true, true, 0);

			pageManager = PageManager.Create(favoriteHubManager, PageStyle.Tabs);
			pageManager.CurrentPageChanged += OnCurrentPageChanged;
			pageManager.PageAdded += OnPageAdded;
			pageManager.PageRemoved += OnPageRemoved;

			pageManager.Show();
			pageManagerBox.PackStart(pageManager, true, true, 0);

			// Statusbar
			Box connectionStatus = new ConnectionStatusbar(connectionManager);
			connectionStatus.Show();
			statusBox.PackStart(connectionStatus, false, true, 0);

			// Events
			favoriteHubManager.HubAdded += OnFavoriteHubCountChanged;
			favoriteHubManager.HubRemoved += OnFavoriteHubCountChanged;
		}

		#endregion

		#region Properties

		public PageStyle PageStyle
		{
			get { return (PageStyle)layoutTabsAction.CurrentValue; }
			set { layoutTabsAction.CurrentValue = (int)value; }
		}

		public bool ToolbarVisible
		{
			get { return toolbarVisibleAction.Active; }
			set { toolbarVisibleAction.Active = value; }
		}

		public Page Current
		{
			get { return pageManager.Current; }
		}

		protected bool IsWorking
		{
			get { return isWorking; }
			set
			{
				if (value != isWorking)
				{
					if (value)
					{
						Window.GdkWindow.Cursor = workingCursor;
						if (timeout == 0)
						{
							progressBar.Visible = true;
							timeout = GLib.Timeout.Add(10, OnProgressTimeout);
						}
					}
					else
					{
						Window.GdkWindow.Cursor = normalCursor;
						if (timeout != 0)
						{
							progressBar.Visible = false;
							GLib.Source.Remove(timeout);
							timeout = 0;
						}
					}
					isWorking = value;
				}
			}
		}

		#endregion

		#region Methods

		public void Add(Page page)
		{
			pageManager.Add(page);
		}

		public void Remove(Page page)
		{
			pageManager.Remove(page);
		}

		public bool Contains(Page page)
		{
			return pageManager.Contains(page);
		}

		public void Present(Page page)
		{
			pageManager.Present(page);
		}

		public T Find<T>(Predicate<T> match) where T : Page
		{
			return pageManager.Find(match);
		}

		public override void Destroy()
		{
			favoriteHubManager.HubAdded -= OnFavoriteHubCountChanged;
			favoriteHubManager.HubRemoved -= OnFavoriteHubCountChanged;

			pageManager.Clear();
			base.Destroy();
		}

		private void UpdateFavoriteHubMenu(MenuToolButton button)
		{
			button.Menu = new Menu();

			foreach (FavoriteHubInfo hubInfo in favoriteHubManager.Hubs)
			{
				if (hubInfo.Name == null || hubInfo.Name.Length == 0)
				{
					continue;
				}

				FavoriteHubInfo hub = hubInfo;

				Uri uri;
				bool validUri = !String.IsNullOrEmpty(hub.Hostname) &&
					StringUtil.TryCreateUri(hub.Hostname, out uri);

				ImageMenuItem item = new ImageMenuItem(hub.Name);

				item.Image = new Image(Util.GetHubIcon(hub.Hostname));
				item.Sensitive = validUri;

				item.Activated += delegate
				{
					HubConnection h = Globals.Runtime.ConnectToHub(hub);
					Globals.Gui.ShowHub(h, true);
				};

				item.Selected += delegate
				{
					string text = hub.Description;
					if (!String.IsNullOrEmpty(text) && hub.Hostname != null)
					{
						text += " - ";
					}
					text += hub.Hostname;

					statusbar.Push (1, text);
				};

				item.Deselected += delegate
				{
					statusbar.Pop (1);
				};

				((Menu)button.Menu).Append(item);
			}

			button.Menu.ShowAll();
		}

		private void UpdateStatus()
		{
			if (currentPage == null)
			{
				IsWorking = false;
				statusbar.Pop(0);
				return;
			}

			statusbar.Pop(0);
			statusbar.Push(0, currentPage.Status != null ?
				currentPage.Status : String.Empty);

			IsWorking = currentPage.IsWorking;
		}

		private void OnWidgetAdd(object obj, AddWidgetArgs args)
		{
			args.Widget.Show();
			mainBox.PackStart(args.Widget, false, true, 0);
		}

		private void OnConnectProxy(object obj, ConnectProxyArgs args)
		{
			MenuItem item = args.Proxy as MenuItem;
			if (item != null)
			{
				if (!itemToAction.ContainsKey(item))
				{
					item.Selected += OnMenuItemSelected;
					item.Deselected += OnMenuItemDeselected;
				}
				itemToAction[item] = args.Action;
			}
		}

		private void OnDisconnectProxy(object obj, DisconnectProxyArgs args)
		{
			MenuItem item = args.Proxy as MenuItem;
			if (item != null)
			{
				if (itemToAction.ContainsKey(item))
				{
					item.Selected -= OnMenuItemSelected;
					item.Deselected -= OnMenuItemDeselected;
					itemToAction.Remove(item);
				}
			}
		}

		private void OnMenuItemSelected(object obj, EventArgs args)
		{
			Action action;
			if (itemToAction.TryGetValue((MenuItem)obj, out action) &&
				action.Tooltip != null)
			{
				statusbar.Push(1, action.Tooltip);
			}
		}

		private void OnMenuItemDeselected(object obj, EventArgs args)
		{
			if (itemToAction.ContainsKey((MenuItem)obj))
			{
				statusbar.Pop(1);
			}
		}

		#region Page manager events

		private void OnCurrentPageChanged(object obj, PageEventArgs args)
		{
			if (currentPage != null)
			{
				currentPage.StatusChanged -= OnStatusChanged;
			}

			// Remove the UI from the previous page
			if (mergeId != 0)
			{
				uim.RemoveUi(mergeId);
				uim.EnsureUpdate();
				mergeId = 0;
			}
			if (pageActions != null)
			{
				uim.RemoveActionGroup(pageActions);
				pageActions = null;
			}

			Page page = args.Page;
			if (page != null)
			{
				page.StatusChanged += OnStatusChanged;

				// Add the UI for current page
				if (page.UI != null)
				{
					mergeId = uim.AddUiFromString(page.UI);
				}
				if (page.Actions != null)
				{
					pageActions = page.Actions;
					uim.InsertActionGroup(pageActions, 0);
				}
			}
			currentPage = page;
			UpdateStatus();

			if (CurrentPageChanged != null)
			{
				CurrentPageChanged(this, args);
			}
		}

		private void OnStatusChanged(object obj, EventArgs args)
		{
			UpdateStatus();
		}

		private void OnPageAdded(object obj, PageEventArgs args)
		{
			closeAction.Sensitive = pageManager.Count > 0;

			if (PageAdded != null)
			{
				PageAdded(this, args);
			}
		}

		private void OnPageRemoved(object obj, PageEventArgs args)
		{
			closeAction.Sensitive = pageManager.Count > 0;

			if (PageRemoved != null)
			{
				PageRemoved(this, args);
			}
		}

		#endregion

		private bool OnProgressTimeout()
		{
			progressBar.Pulse();
			return true;
		}

		#region Action handlers

		private void OnHubs(object obj, EventArgs args)
		{
			Globals.Gui.EditHubs();
		}

		private void OnSearch(object obj, EventArgs args)
		{
			Globals.Gui.ShowSearch(true);
		}

		private void OnPreferences(object obj, EventArgs args)
		{
			Globals.Gui.EditPreferences();
		}

		private void OnClose(object obj, EventArgs args)
		{
			if (pageManager.Current != null)
			{
				pageManager.Remove(pageManager.Current);
			}
		}

		private void OnQuit(object obj, EventArgs args)
		{
			Destroy();
		}

		private void OnDownloads(object obj, EventArgs args)
		{
			Globals.Gui.ShowDownloads(true);
		}

		private void OnUploads(object obj, EventArgs args)
		{
			Globals.Gui.ShowUploads(true);
		}

		private void OnToggleToolbar(object obj, EventArgs args)
		{
			ToggleAction toggleToolbar = (ToggleAction)obj;
			toolbar.Visible = toggleToolbar.Active;
		}

		private void OnAbout(object obj, EventArgs args)
		{
			AboutDialog about = new AboutDialog();

			about.TransientFor = Window;
			about.Run();
			about.Destroy();
		}

		#endregion

		private void OnLayoutChanged(object obj, ChangedArgs args)
		{
			PageStyle pageStyle = (PageStyle)args.Current.Value;

			PageManager oldManager = pageManager;
			pageManagerBox.Remove(oldManager);

			pageManager = PageManager.Create(favoriteHubManager, pageStyle);
			pageManagerBox.PackStart(pageManager, true, true, 0);
			pageManager.Show();

			// Disconnect the old manager
			oldManager.CurrentPageChanged -= OnCurrentPageChanged;
			oldManager.PageAdded -= OnPageAdded;
			oldManager.PageRemoved -= OnPageRemoved;

			// Move the pages to the new manager
			Page current = oldManager.Current;
			while (oldManager.Count > 0)
			{
				Page page = oldManager[0];
				oldManager.Remove(page);

				pageManager.Add(page);
			}
			if (current != null)
			{
				pageManager.Present(current);
			}

			// Connect the new manager
			pageManager.CurrentPageChanged += OnCurrentPageChanged;
			pageManager.PageAdded += OnPageAdded;
			pageManager.PageRemoved += OnPageRemoved;

			oldManager.Destroy();
		}

		private void OnFavoriteHubCountChanged(object obj,
			FavoriteHubEventArgs args)
		{
			Application.Invoke(delegate
			{
				hubToolButton.Menu = favoriteHubManager.HubCount != 0 ? new Menu() : null;
			});
		}

		#endregion
	}
}
